<div class="content-section introduction">
    <div class="feature-intro">
        <h1>VirtualScroller</h1>
        <p>VirtualScroller is an efficient way of rendering lists by displaying a small subset of data in the viewport at any time.</p>
    </div>
    <app-demoActions github="virtualscroller" stackblitz="virtualscroller-demo"></app-demoActions>
</div>

<div class="content-section implementation">
    <div class="card">
        <h5>Lazy Loading</h5>
        <p-virtualScroller [value]="virtualProducts" scrollHeight="450px" [itemSize]="100" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)">
            <ng-template pTemplate="header">
                List of Products
            </ng-template>
            <ng-template let-product pTemplate="item">
                <div class="product-item">
                    <div class="image-container">
                        <img src="assets/showcase/images/demo/product/{{product.image}}" [alt]="product.name" class="product-image" />
                    </div>
                    <div class="product-list-detail">
                        <h5 class="mb-2">{{product.name}}</h5>
                        <i class="pi pi-tag product-category-icon"></i>
                        <span class="product-category">{{product.category}}</span>
                    </div>
                    <div class="product-list-action">
                        <h6 class="mb-2">${{product.price}}</h6>
                        <span [class]="'product-badge status-' + product.inventoryStatus.toLowerCase()">{{product.inventoryStatus}}</span>
                    </div>
                </div>
            </ng-template>
            <ng-template let-product pTemplate="loadingItem">
                <div class="product-item loading-item">
                    <div class="image-container"></div>
                    <div class="product-list-detail">
                        <h5 class="mb-2"></h5>
                        <i class="product-category-icon"></i>
                        <span class="product-category"></span>
                    </div>
                    <div class="product-list-action">
                        <h6 class="mb-2"></h6>
                        <span class="product-badge"></span>
                    </div>
                </div>
            </ng-template>
        </p-virtualScroller>
    </div>

    <div class="card">
        <h5>Prepopulated List</h5>
        <p-virtualScroller [value]="products" scrollHeight="500px" [itemSize]="100">
            <ng-template pTemplate="header">
                <div class="flex align-items-center justify-content-center gap-3 flex-wrap">
                    List of Products
                    <p-dropdown [options]="sortOptions" [(ngModel)]="sortKey" placeholder="Sort By" (onChange)="onSortChange()" [style]="{'min-width':'10em'}"></p-dropdown>
                </div>
            </ng-template>
            <ng-template let-product pTemplate="item">
                <div class="product-item">
                    <div class="image-container">
                        <img src="assets/showcase/images/demo/product/{{product.image}}" [alt]="product.name" class="product-image" />
                    </div>
                    <div class="product-list-detail">
                        <h5 class="mb-2">{{product.name}}</h5>
                        <i class="pi pi-tag product-category-icon"></i>
                        <span class="product-category">{{product.category}}</span>
                    </div>
                    <div class="product-list-action">
                        <h6 class="mb-2">${{product.price}}</h6>
                        <span [class]="'product-badge status-' + product.inventoryStatus.toLowerCase()">{{product.inventoryStatus}}</span>
                    </div>
                </div>
            </ng-template>
        </p-virtualScroller>
    </div>
</div>

<div class="content-section documentation">
    <p-tabView>
        <p-tabPanel header="Documentation">
            <h5>Import</h5>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123;VirtualScrollerModule&#125; from 'primeng/virtualscroller';
</app-code>

            <h5>Getting Started</h5>
            <p>VirtualScroller requires a collection of items as its value, height of an item size, height of the scrollable viewport and a ng-template to display
                    where each item can be accessed using the implicit variable.</p>

            <p>Throughout the samples, a car interface having vin, brand, year and color properties are used
                to define an object to be displayed by the VirtualScroller. Cars are loaded by a CarService that
                connects to a server to fetch the cars with a Promise. Note that this is for demo purposes only,
                any data source such as an Observable can be used as an alternative as well.</p>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export interface Car &#123;
    vin;
    year;
    brand;
    color;
&#125;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123; HttpClient &#125; from '@angular/common/http';
import &#123; Injectable &#125; from '@angular/core';

import &#123; Car &#125; from '../domain/car';

@Injectable()
export class CarService &#123;

    constructor(private http: HttpClient) &#123;&#125;

    getCarsSmall() &#123;
        return this.http.get('/showcase/resources/data/cars-small.json')
                    .toPromise()
                    .then(res => &lt;Car[]&gt; res.data)
                    .then(data => &#123; return data; &#125;);
    &#125;
&#125;
</app-code>

            <p>Here is a sample VirtualScroller that displays a list of cars loaded from a remote datasource.</p>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class VirtualScrollerDemo implements OnInit &#123;

    cars: Car[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsLarge().then(cars => this.cars = cars);
    &#125;
&#125;
</app-code>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150"&gt;
    &lt;ng-template pTemplate="item" let-car&gt;
        Car content
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

            <h5>Sections</h5>
            <p>Header and Footer are the two sections that are capable of displaying custom content.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-virtualScroller [value]="cars" scrollHeight="500px" [itemSize]="150"&gt;
    &lt;ng-template pTemplate="header"&gt;Header Content&lt;/ng-template&gt;
    &lt;ng-template pTemplate="item" let-car&gt;
    Car content
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="footer"&gt;Footer Content&lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

            <h5>Lazy Loading</h5>
            <p>Lazy mode is handy to deal with large datasets where instead of loading the entire data, small chunks of data are loaded on demand by invoking
             onLazyLoad callback everytime scrolling requires a new chunk. To implement lazy loading,
            enable <i>lazy</i> attribute, initialize your data as a placeholder with a length and finally implement a method callback using <i>onLazyLoad</i> that actually loads a chunk from a datasource. onLazyLoad gets an event object
            that contains information about the chunk of data to load such as the index and number of items to load. Notice that a new template called loadingItem is also required to display as a placeholder while the new items are being loaded.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-virtualScroller [value]="virtualCars" scrollHeight="500px" [itemSize]="150"
    [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"&gt;
    &lt;ng-template let-car pTemplate="item"&gt;
        Car content
    &lt;/ng-template&gt;
    &lt;ng-template let-car pTemplate="loadingItem"&gt;
        Loading...
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class LazyVirtualScrollerDemo implements OnInit &#123;

    virtualCars: Car[];

    ngOnInit() &#123;
        this.cars = Array.from(&#123;length: 10000&#125;).map(() => this.carService.generateCar());
        this.virtualCars =  Array.from(&#123;length: 10000&#125;);
    &#125;

    loadCarsLazy(event: LazyLoadEvent) &#123;
        //simulate remote connection with a timeout
        setTimeout(() => &#123;
            //load data of required page
            let loadedCars = this.cars.slice(event.first, event.last);

            //populate page of virtual cars
            Array.prototype.splice.apply(this.virtualCars, [...[event.first, event.rows], ...loadedCars]);

            //trigger change detection
            event.forceUpdate();
        &#125;, 1000);
    &#125;

&#125;
</app-code>

            <h5>Programmatic Scroll</h5>
            <p>Scrolling to a specific index can be done with the <i>scrollToIndex</i> function.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;button pButton label="Reset" (click)="reset"&gt;&lt;/button&gt;

&lt;p-virtualScroller #vs [value]="cars" scrollHeight="500px" [itemSize]="150"&gt;
    &lt;ng-template pTemplate="item" let-car&gt;
        Car content
    &lt;/ng-template&gt;
&lt;/p-virtualScroller&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
@ViewChild('vs') vs: VirtualScroller;

reset() &#123;
    this.vs.scrollToIndex(0, 'smooth');
&#125;
</app-code>

            <h5>Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>delay</td>
                            <td>number</td>
                            <td>250</td>
                            <td>Threshold in milliseconds to delay lazy loading during scrolling.</td>
                        </tr>
                        <tr>
                            <td>itemSize</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Height of an item in the list.</td>
                        </tr>
                        <tr>
                            <td>lazy</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Defines if data is loaded and interacted with in lazy manner.</td>
                        </tr>
                        <tr>
                            <td>scrollHeight</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Max height of the content area in inline mode.</td>
                        </tr>
                        <tr>
                            <td>style</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Inline style of the component.</td>
                        </tr>
                        <tr>
                            <td>styleClass</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Style class of the component.</td>
                        </tr>
                        <tr>
                            <td>value</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of objects to display.</td>
                        </tr>
                        <tr>
                            <td>options</td>
                            <td>ScrollerOptions</td>
                            <td>false</td>
                            <td>Whether to use the scroller feature. The properties of <a href="#" [routerLink]="['/scroller']">scroller</a> component can be used like an object in it.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Events</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                    <tr>
                        <th>Name</th>
                        <th>Parameters</th>
                        <th>Description</th>
                    </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>onLazyLoad</td>
                            <td>event.first = First row offset <br>
                                event.last = Last row offset <br>
                                event.rows = Number of rows per page <br></td>
                            <td>Callback to invoke in lazy mode to load new data.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Methods</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Parameters</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>scrollToIndex</td>
                            <td>index: Index of the item.<br />
                                mode: Scroll mode e.g. 'auto' or 'smooth'
                            </td>
                            <td>Scrolls to the item with the given index.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Templates</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                        <thead>
                            <tr>
                                <th>Name</th>
                                <th>Parameters</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <td>header</td>
                                <td>-</td>
                            </tr>
                            <tr>
                                <td>item</td>
                                <td>$implicit: Data of the option <br />
                                    index: Index of the option <br />
                                    count: Count of the options <br />
                                    first: First row offset <br/>
                                    last: Last row offset </td>
                            </tr>
                            <tr>
                                <td>loadingItem</td>
                                <td>$implicit: Data of the option <br />
                                    index: Index of the option <br />
                                    count: Count of the options <br />
                                    first: First row offset <br/>
                                    last: Last row offset </td>
                            </tr>
                            <tr>
                                <td>footer</td>
                                <td>-</td>
                            </tr>
                        </tbody>
                </table>
            </div>

            <h5>Styling</h5>
            <p>Following is the list of structural style classes, for theming classes visit <a href="#" [routerLink]="['/theming']">theming page</a>.</p>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Element</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>p-virtualscroller</td>
                            <td>Container element.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-header</td>
                            <td>Header section.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-footer</td>
                            <td>Footer section.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-content</td>
                            <td>Content section.</td>
                        </tr>
                        <tr>
                            <td>p-virtualscroller-list</td>
                            <td>List element.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Dependencies</h5>
            <p>None.</p>
        </p-tabPanel>

        <p-tabPanel header="Source">
            <a href="https://github.com/primefaces/primeng/tree/master/src/app/showcase/components/virtualscroller" class="btn-viewsource" target="_blank">
                <span>View on GitHub</span>
            </a>
            <a href="https://stackblitz.com/edit/primeng-virtualscroller-demo" class="btn-viewsource" style="margin-left: .5em;" target="_blank">
                <span>Edit in StackBlitz</span>
            </a>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;div class="card"&gt;
    &lt;h5&gt;Lazy Loading&lt;/h5&gt;
    &lt;p-virtualScroller [value]="virtualProducts" scrollHeight="450px" [itemSize]="100" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"&gt;
        &lt;ng-template pTemplate="header"&gt;
            List of Products
        &lt;/ng-template&gt;
        &lt;ng-template let-product pTemplate="item"&gt;
            &lt;div class="product-item"&gt;
                &lt;div class="image-container"&gt;
                    &lt;img src="assets/showcase/images/demo/product/&#123;&#123;product.image&#125;&#125;" [alt]="product.name" class="product-image" /&gt;
                &lt;/div&gt;
                &lt;div class="product-list-detail"&gt;
                    &lt;h5 class="mb-2"&gt;&#123;&#123;product.name&#125;&#125;&lt;/h5&gt;
                    &lt;i class="pi pi-tag product-category-icon"&gt;&lt;/i&gt;
                    &lt;span class="product-category"&gt;&#123;&#123;product.category&#125;&#125;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class="product-list-action"&gt;
                    &lt;h6 class="mb-2"&gt;$&#123;&#123;product.price&#125;&#125;&lt;/h6&gt;
                    &lt;span [class]="'product-badge status-' + product.inventoryStatus.toLowerCase()"&gt;&#123;&#123;product.inventoryStatus&#125;&#125;&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/ng-template&gt;
        &lt;ng-template let-product pTemplate="loadingItem"&gt;
            &lt;div class="product-item loading-item"&gt;
                &lt;div class="image-container"&gt;&lt;/div&gt;
                &lt;div class="product-list-detail"&gt;
                    &lt;h5 class="mb-2"&gt;&lt;/h5&gt;
                    &lt;i class="product-category-icon"&gt;&lt;/i&gt;
                    &lt;span class="product-category"&gt;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class="product-list-action"&gt;
                    &lt;h6 class="mb-2"&gt;&lt;/h6&gt;
                    &lt;span class="product-badge"&gt;&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/ng-template&gt;
    &lt;/p-virtualScroller&gt;
&lt;/div&gt;

&lt;div class="card"&gt;
    &lt;h5&gt;Prepopulated List&lt;/h5&gt;
    &lt;p-virtualScroller [value]="products" scrollHeight="500px" [itemSize]="100"&gt;
        &lt;ng-template pTemplate="header"&gt;
            &lt;div class="flex align-items-center justify-content-center gap-3 flex-wrap"&gt;
                List of Products
                &lt;p-dropdown [options]="sortOptions" [(ngModel)]="sortKey" placeholder="Sort By" (onChange)="onSortChange()" [style]="&#123;'min-width':'10em'&#125;"&gt;&lt;/p-dropdown&gt;
            &lt;/div&gt;
        &lt;/ng-template&gt;
        &lt;ng-template let-product pTemplate="item"&gt;
            &lt;div class="product-item"&gt;
                &lt;div class="image-container"&gt;
                    &lt;img src="assets/showcase/images/demo/product/&#123;&#123;product.image&#125;&#125;" [alt]="product.name" class="product-image" /&gt;
                &lt;/div&gt;
                &lt;div class="product-list-detail"&gt;
                    &lt;h5 class="mb-2"&gt;&#123;&#123;product.name&#125;&#125;&lt;/h5&gt;
                    &lt;i class="pi pi-tag product-category-icon"&gt;&lt;/i&gt;
                    &lt;span class="product-category"&gt;&#123;&#123;product.category&#125;&#125;&lt;/span&gt;
                &lt;/div&gt;
                &lt;div class="product-list-action"&gt;
                    &lt;h6 class="mb-2"&gt;$&#123;&#123;product.price&#125;&#125;&lt;/h6&gt;
                    &lt;span [class]="'product-badge status-' + product.inventoryStatus.toLowerCase()"&gt;&#123;&#123;product.inventoryStatus&#125;&#125;&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/ng-template&gt;
    &lt;/p-virtualScroller&gt;
&lt;/div&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123; Component,OnInit &#125; from '@angular/core';
import &#123; LazyLoadEvent,SelectItem &#125; from 'primeng/api';
import &#123; Product &#125; from '../../domain/product';
import &#123; ProductService &#125; from '../../service/productservice';

@Component(&#123;
    templateUrl: './virtualscrollerdemo.html',
    styleUrls: ['./virtualscrollerdemo.scss']
&#125;)
export class VirtualScrollerDemo implements OnInit &#123;

    products: Product[];

    virtualProducts: Product[];

    sortKey: string;

    sortOptions: SelectItem[];

    constructor(private productService: ProductService) &#123;&#125;

    ngOnInit() &#123;
        this.products = Array.from(&#123;length: 10000&#125;).map(() => this.productService.generatePrduct());
        this.virtualProducts = Array.from(&#123;length: 10000&#125;);

        this.sortOptions = [
            &#123;label: 'Cheapest First', value: 'price'&#125;,
            &#123;label: 'Expensive First', value: '!price'&#125;
        ];
    &#125;

    loadCarsLazy(event: LazyLoadEvent) &#123;
        // simulate remote connection with a timeout
        setTimeout(() => &#123;
            //load data of required page
            let loadedProducts = this.products.slice(event.first, (event.first + event.rows));

            //populate page of virtual cars
            Array.prototype.splice.apply(this.virtualProducts, [...[event.first, event.rows], ...loadedProducts]);

            //trigger change detection
            event.forceUpdate();
        &#125;, 1000);
    &#125;

    onSortChange() &#123;
        if (this.sortKey.indexOf('!') === 0)
            this.sort(-1);
        else
            this.sort(1);
    &#125;

    sort(order: number): void &#123;
        let products = [...this.products];
        products.sort((data1, data2) => &#123;
            let value1 = data1.price;
            let value2 = data2.price;
            let result = (value1 &lt; value2) ? -1 : (value1 > value2) ? 1 : 0;

            return (order * result);
        &#125;);

        this.products = products;
    &#125;
&#125;
</app-code>
        </p-tabPanel>
        <p-tabPanel header="StackBlitz">
            <ng-template pTemplate="content">
                <iframe src="https://stackblitz.com/edit/primeng-virtualscroller-demo?embed=1" style="width: 100%; height: 768px; border: none;"></iframe>
            </ng-template>
        </p-tabPanel>
    </p-tabView>
</div>
